﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Workflow.ComponentModel;
using System.Workflow.ComponentModel.Compiler;
using Microsoft.Crm.Sdk;
using Microsoft.Crm.Sdk.Query;
using Microsoft.Crm.SdkTypeProxy;
using Microsoft.Crm.Workflow;
using System.Web.Services.Protocols;

namespace CrmContrib.Workflow.Activities.Invoicing
{
    [CrmWorkflowActivity("Get invoice contact by relationship", "CrmContrib \\ Invoicing")]
    public class GetContactByRelationshipActivity : Activity
    {
        [CrmInput("Invoice")]
        [CrmReferenceTarget("invoice")]
        [ValidationOption(ValidationOption.Required)]
        public Lookup Invoice
        {
            get { return (Lookup)base.GetValue(InvoiceProperty); }
            set { base.SetValue(InvoiceProperty, value); }
        }

        public static readonly DependencyProperty InvoiceProperty = DependencyProperty.Register("Invoice", typeof(Lookup), typeof(GetContactByRelationshipActivity));

        [CrmInput("Customer Relationship")]
        [CrmReferenceTarget("relationshiprole")]
        [ValidationOption(ValidationOption.Required)]
        public Lookup CustomerRelationship
        {
            get { return (Lookup)base.GetValue(CustomerRelationshipProperty); }
            set { base.SetValue(CustomerRelationshipProperty, value); }
        }

        public static readonly DependencyProperty CustomerRelationshipProperty = DependencyProperty.Register("CustomerRelationship", typeof(Lookup), typeof(GetContactByRelationshipActivity));

        private Guid LoadOpportunityFromInvoice(ICrmService crmService)
        {
            var projection = new ColumnSet();
            projection.Attributes.Add("salesorderid");
            projection.Attributes.Add("opportunityid");
            projection.AddColumns(new[] { "salesorderid" });

            var entity = new TargetRetrieveInvoice { EntityId = Invoice.Value };

            var response = (RetrieveResponse)crmService.Execute(new RetrieveRequest { ColumnSet = projection, Target = entity, ReturnDynamicEntities = true });

            DynamicEntity invoice = (DynamicEntity)response.BusinessEntity;
            if (invoice.Properties.Contains("opportunityid"))
                return ((CrmReference)invoice.Properties["opportunityid"]).Value;

            if (!invoice.Properties.Contains("salesorderid"))
                throw new ArgumentException("no associated sales order");

            var orderEntity = new TargetRetrieveSalesOrder { EntityId = ((CrmReference)invoice.Properties["salesorderid"]).Value };
            var orderProjection = new ColumnSet();
            orderProjection.Attributes.Add("opportunityid");
            var orderResponse = (RetrieveResponse)crmService.Execute(new RetrieveRequest { ColumnSet = orderProjection, Target = orderEntity, ReturnDynamicEntities = true });
            var order = (DynamicEntity)orderResponse.BusinessEntity;

            return ((CrmReference)order.Properties["opportunityid"]).Value;
        }

        private IEnumerable<Guid> LoadOpportunityRelationshipsWithRole(ICrmService crmService, Guid opportunityId)
        {
            //Get the opportunity relationship for the opportunity with the given customer role
            var query = new QueryExpression { EntityName = EntityName.customeropportunityrole.ToString(), ColumnSet = new ColumnSet() };
            query.ColumnSet.AddColumn("customerid");
            query.Criteria.AddCondition("opportunityid", ConditionOperator.Equal, opportunityId);
            query.Criteria.AddCondition("opportunityroleid", ConditionOperator.Equal, CustomerRelationship.Value);

            //get the customer account/contact id
            var response = (RetrieveMultipleResponse)crmService.Execute(new RetrieveMultipleRequest { ReturnDynamicEntities = true, Query = query });
            foreach (DynamicEntity item in response.BusinessEntityCollection.BusinessEntities)
            {
                yield return ((CrmReference)item["customerid"]).Value;
            }
        }

        private Guid LoadContact(ICrmService crmService, Guid customerId)
        {
            var projection = new ColumnSet();
            projection.Attributes.Add("contactid");

            var target = new TargetRetrieveContact { EntityId = customerId };
            try
            {
                var response = (RetrieveResponse)crmService.Execute(new RetrieveRequest { ColumnSet = projection, Target = target, ReturnDynamicEntities = true });
                DynamicEntity contact = (DynamicEntity)response.BusinessEntity;
                if (contact.Properties.Contains("contactid"))
                    return ((Key)contact["contactid"]).Value;
            }
            catch (SoapException)
            {
                HasAContact = new CrmBoolean(false);
            }
            return Guid.Empty;
        }

        protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
        {
            var contextService = executionContext.GetService<IContextService>();
            var workflowContext = contextService.Context;
            var crmService = workflowContext.CreateCrmService();

            var customerId = LoadOpportunityRelationshipsWithRole(crmService, LoadOpportunityFromInvoice(crmService)).FirstOrDefault();

            HasAContact = new CrmBoolean(false);

            var contact = LoadContact(crmService, customerId);
            var isContactEntity = contact == Guid.Empty ? false : true;
            if (isContactEntity)
            {
                Contact = new Lookup("contact", contact);
                HasAContact = new CrmBoolean(true);
            }

            return base.Execute(executionContext);
        }

        [CrmOutput("Contact")]
        [CrmReferenceTarget("contact")]
        public Lookup Contact
        {
            get { return (Lookup)base.GetValue(ContactProperty); }
            set { base.SetValue(ContactProperty, value); }
        }

        public static readonly DependencyProperty ContactProperty = DependencyProperty.Register("Contact", typeof(Lookup), typeof(GetContactByRelationshipActivity));

        [CrmOutput("Has a contact")]
        public CrmBoolean HasAContact
        {
            get { return (CrmBoolean)base.GetValue(HasAContactProperty); }
            set { base.SetValue(HasAContactProperty, value); }
        }

        public static readonly DependencyProperty HasAContactProperty = DependencyProperty.Register("HasAContact", typeof(CrmBoolean), typeof(GetContactByRelationshipActivity));

    }
}
